package com.fly.user.component.aspect;

import com.fly.user.component.annotion.UserIdAnnotion;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.dozer.util.ReflectionUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @description 用户主键切面
 * @author: fly
 * @date: 2018/12/19 10:09
 */
@Component
@Aspect
@Slf4j
public class UserIdAspect {
    @Pointcut("@annotation(com.fly.user.component.annotion.UserIdAnnotion)")
    public void whichUserId() {
    }

    @Around("whichUserId()")
    public Object pushUserId(ProceedingJoinPoint point) throws Throwable {
        Method method = getMethod(point);
        UserIdAnnotion annotation = method.getAnnotation(UserIdAnnotion.class);

        Object[] args = point.getArgs();
        for (int i = 0; i < args.length; i++) {
            args[i] = trySetUserId(annotation, args[i]);
        }

        return point.proceed(args);
    }

    private Object trySetUserId(UserIdAnnotion annotation, Object argument) {
        try {
            return setUserId(annotation, argument);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return argument;
    }

    private Object setUserId(UserIdAnnotion annotation, Object argument) {
        Field userIdField = getUserIdField(annotation, argument);
        if (userIdField == null) {
            return argument;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getDetails() != null) {
            if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
                Object user = ((OAuth2AuthenticationDetails) authentication.getDetails()).getDecodedDetails();
                Long userId = getUserId(user);
                invokeSetUserId(userIdField, argument, userId);
            }
        }
        return argument;
    }

    private Field getUserIdField(UserIdAnnotion annotation, Object argument) {
        try {
            Class clazz = argument.getClass();
            return ReflectionUtils.getFieldFromBean(clazz, annotation.value());
        } catch (Exception e) {
            log.error("UserIdAspect getUserIdField Exception", e);
        }
        return null;
    }

    private Long getUserId(Object user) {
        try {
            Class clazz = user.getClass();
            Field field = ReflectionUtils.getFieldFromBean(clazz, "userId");
            return (Long) field.get(user);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void invokeSetUserId(Field userIdField, Object argument, Long userId) {
        try {
            userIdField.set(argument, userId);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取被拦截方法对象
     * <p>
     * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
     * 而缓存的注解在实现类的方法上
     * 所以应该使用反射获取当前对象的方法对象
     */
    private Method getMethod(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        return signature.getMethod();
    }
}
